package indv.Cning.cfengsecuritydemo.jwt;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import indv.Cning.cfengsecuritydemo.domain.JwtUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Cfeng
 * @date 2022/7/24
 * 封装JWT相关功能用于JWT验证,要进行序列化进行网络传输
 */

@Component
@RequiredArgsConstructor
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -275260418023383596L;

    //设置token的有效期,单位为s
    private static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;

    @Value("${spring.jwt.secret}")
    private String secret;
    //Json工具类
    private final ObjectMapper objectMapper;

    /**
     * 从JWT中获取用户名
     */
    public String getUserNameFromToken(String token) {
        return this.getClaimFromToken(token,Claims::getSubject);
    }

    /**
     * 从JWT中获取过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        return this.getClaimFromToken(token,Claims::getExpiration);
    }

    /**
     * 从JWT中获取声明Claim,这里直接创建一个功能性接口直接使用方法引用
     */
    public <T> T getClaimFromToken(String token, Function<Claims,T> claimsResolvers) {
        final  Claims claims = this.getAllClaimsFromToken(token);
        return claimsResolvers.apply(claims);
    }

    /**
     * 通过密钥获取JWT中所有的claims
     * secret就可以得到
     */
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    /**
     * token是否过期
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = this.getExpirationDateFromToken(token);
        return expiration.before(new Date()); //日期比较直接使用before，new Date()当前时间
    }

    /**
     * 为用户生成Token
     */
    public String generateToken(UserDetails userDetails) throws JsonProcessingException {
        Map<String,Object> claims = new HashMap<>();
        //根据需求在claims新增状态信息，jsonString格式
        JwtUser user = new JwtUser().setUserName(userDetails.getUsername()).setUserPwd(userDetails.getPassword());
        //权限信息
        user.setAuthorities(userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
        //加入claim
        claims.put("principal",objectMapper.writeValueAsString(user));
        //将上面的claim等信息一起生成token
        //JWT中的subject只是principal.name
        return this.doGenerateToken(claims,userDetails.getUsername());
    }

    /**
     * 生成Token： 定义令牌的声明信息 eg 主体，过期时间； 使用你HS512算法与配置好的密钥对JWT进行加密，压缩JWT
     */
    public String doGenerateToken(Map<String,Object> claims, String subject) {
        return Jwts
                .builder()
                //定义令牌信息
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis())) //令牌创建时间
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                //签名加密,使用密钥
                .signWith(SignatureAlgorithm.HS512,secret)
                //压缩为string
                .compact();
    }

    /**
     * 验证token
     */
    public Boolean validateToken(String token, String userName) {
        return Objects.equals(this.getUserNameFromToken(token),userName)  && !this.isTokenExpired(token);
    }
}
